{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 图片分类1：不同类别放到不同文件夹"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:10:01.091946Z",
     "start_time": "2025-01-20T13:09:51.939586Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "```shell\n",
    "$ tree -L 2 archive \n",
    "archive\n",
    "├── monkey_labels.txt\n",
    "├── training\n",
    "│   ├── n0\n",
    "│   ├── n1\n",
    "│   ├── n2\n",
    "│   ├── n3\n",
    "│   ├── n4\n",
    "│   ├── n5\n",
    "│   ├── n6\n",
    "│   ├── n7\n",
    "│   ├── n8\n",
    "│   └── n9\n",
    "└── validation\n",
    "    ├── n0\n",
    "    ├── n1\n",
    "    ├── n2\n",
    "    ├── n3\n",
    "    ├── n4\n",
    "    ├── n5\n",
    "    ├── n6\n",
    "    ├── n7\n",
    "    ├── n8\n",
    "    └── n9\n",
    "\n",
    "22 directories, 1 file\n",
    "```"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "查看当前目录"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T12:09:01.746182Z",
     "start_time": "2025-01-20T12:09:01.126970Z"
    }
   },
   "cell_type": "code",
   "source": "! ls",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "01-classification_model-cnn.ipynb\n",
      "02_classification_model-cnn-selu.ipynb\n",
      "03_classification_model-separable_cnn.ipynb\n",
      "04_10_monkeys_model_2.ipynb\n",
      "05_10_monkeys_model_2_resnet50_finetune_2.ipynb\n",
      "06_cifar10_model_2.ipynb\n",
      "checkpoints\n",
      "colab杩愯�岀粨鏋�\n",
      "data\n",
      "day23_璇惧爞绗旇��.docx\n",
      "model_CNN\n",
      "model_CNN.png\n",
      "runs\n",
      "week4 鍥炲綊-闆嗘垚瀛︿範-娣卞害瀛︿範瀹炵幇鍒嗙被涓庡洖褰�-鍗风Н浠嬬粛.xmind\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T12:16:18.219367Z",
     "start_time": "2025-01-20T12:16:18.165656Z"
    }
   },
   "cell_type": "code",
   "source": "! tree checkpoints",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "卷 Data 的文件夹 PATH 列表\n",
      "卷序列号为 60A8-049D\n",
      "D:\\DAILYCODEANDNOTE\\DAY23\\CHECKPOINTS\n",
      "├─cnn-relu\n",
      "├─cnn-selu\n",
      "└─dsc-selu\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:11:06.700174Z",
     "start_time": "2025-01-20T13:11:06.661957Z"
    }
   },
   "cell_type": "code",
   "source": "! tree archive",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "卷 Data 的文件夹 PATH 列表\n",
      "卷序列号为 60A8-049D\n",
      "D:\\DAILYCODEANDNOTE\\DAY23\\ARCHIVE\n",
      "├─training\n",
      "│  ├─n0\n",
      "│  ├─n1\n",
      "│  ├─n2\n",
      "│  ├─n3\n",
      "│  ├─n4\n",
      "│  ├─n5\n",
      "│  ├─n6\n",
      "│  ├─n7\n",
      "│  ├─n8\n",
      "│  └─n9\n",
      "└─validation\n",
      "    ├─n0\n",
      "    ├─n1\n",
      "    ├─n2\n",
      "    ├─n3\n",
      "    ├─n4\n",
      "    ├─n5\n",
      "    ├─n6\n",
      "    ├─n7\n",
      "    ├─n8\n",
      "    └─n9\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:27:24.151414Z",
     "start_time": "2025-01-20T13:27:24.139013Z"
    }
   },
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor, Resize, Compose, ConvertImageDtype, Normalize\n",
    "\n",
    "\n",
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\"./archive/\")\n",
    "\n",
    "# 预先设定的图片尺寸\n",
    "img_h, img_w = 128, 128\n",
    "transform = Compose([\n",
    "    Resize((img_h, img_w)), # 图片缩放\n",
    "    ToTensor(),\n",
    "    # 预先统计的\n",
    "    Normalize([0.4363, 0.4328, 0.3291], [0.2085, 0.2032, 0.1988]),\n",
    "    ConvertImageDtype(torch.float), # 转换为float类型\n",
    "]) #数据预处理\n",
    "\n",
    "class MonkeyDataset(datasets.ImageFolder):\n",
    "    def __init__(self, mode, transform=None):\n",
    "        if mode == \"train\":\n",
    "            root = DATA_DIR / \"training\"\n",
    "        elif mode == \"val\":\n",
    "            root = DATA_DIR / \"validation\"\n",
    "        else:\n",
    "            raise ValueError(\"mode should be one of the following: train, val, but got {}\".format(mode))\n",
    "        super().__init__(root, transform) # 调用父类init方法\n",
    "        # self.imgs = self.samples # self.samples里边是图片路径及标签 [(path, label), (path, label),...]\n",
    "        self.targets = [s[1] for s in self.samples] # 标签取出来\n",
    "\n",
    "\n",
    "train_ds = MonkeyDataset(\"train\", transform=transform)\n",
    "val_ds = MonkeyDataset(\"val\", transform=transform)\n",
    "\n",
    "print(\"load {} images from training dataset\".format(len(train_ds)))\n",
    "print(\"load {} images from validation dataset\".format(len(val_ds)))"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load 1097 images from training dataset\n",
      "load 272 images from validation dataset\n"
     ]
    }
   ],
   "execution_count": 14
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:13:25.033780Z",
     "start_time": "2025-01-20T13:13:25.029437Z"
    }
   },
   "source": [
    "# 数据类别\n",
    "train_ds.classes"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9']"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "cell_type": "code",
   "source": [
    "train_ds.class_to_idx"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-20T13:13:27.183979Z",
     "start_time": "2025-01-20T13:13:27.178496Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'n0': 0,\n",
       " 'n1': 1,\n",
       " 'n2': 2,\n",
       " 'n3': 3,\n",
       " 'n4': 4,\n",
       " 'n5': 5,\n",
       " 'n6': 6,\n",
       " 'n7': 7,\n",
       " 'n8': 8,\n",
       " 'n9': 9}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:13:43.365712Z",
     "start_time": "2025-01-20T13:13:43.361313Z"
    }
   },
   "source": [
    "# # 图片路径 及 标签\n",
    "i=0\n",
    "for fpath, label in train_ds.samples:\n",
    "    print(fpath, label)\n",
    "    i += 1\n",
    "    if i == 10:\n",
    "        break\n",
    "#\n",
    "# #这个和之前的dataset完全一致\n",
    "# for img, label in train_ds:\n",
    "#     # c, h, w  label\n",
    "#     print(img, label)\n",
    "#     break"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "archive\\training\\n0\\n0018.jpg 0\n",
      "archive\\training\\n0\\n0019.jpg 0\n",
      "archive\\training\\n0\\n0020.jpg 0\n",
      "archive\\training\\n0\\n0021.jpg 0\n",
      "archive\\training\\n0\\n0022.jpg 0\n",
      "archive\\training\\n0\\n0023.jpg 0\n",
      "archive\\training\\n0\\n0024.jpg 0\n",
      "archive\\training\\n0\\n0025.jpg 0\n",
      "archive\\training\\n0\\n0026.jpg 0\n",
      "archive\\training\\n0\\n0027.jpg 0\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "cell_type": "code",
   "source": [
    "#因为有3通道，所有有3个mean和std\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[:, :, :].mean(dim=(1, 2))\n",
    "        std += img[:, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-20T13:19:24.044738Z",
     "start_time": "2025-01-20T13:19:07.932261Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.4363, 0.4328, 0.3291]), tensor([0.2085, 0.2032, 0.1988]))\n"
     ]
    }
   ],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:22:24.958278Z",
     "start_time": "2025-01-20T13:22:24.955033Z"
    }
   },
   "cell_type": "code",
   "source": "len(train_ds)",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1097"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        # 0轴表示通道数，[1:2,:,:]表示对第二个通道进行操作，[2:3,:,:]表示对第三个通道进行操作\n",
    "        mean += img[1:2, :, :].mean(dim=(1, 2))\n",
    "        std += img[1:2, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-20T13:28:09.649365Z",
     "start_time": "2025-01-20T13:28:09.643704Z"
    }
   },
   "outputs": [],
   "execution_count": 19
  },
  {
   "cell_type": "code",
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[2:3, :, :].mean(dim=(1, 2))\n",
    "        std += img[2:3, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-20T13:28:02.443147Z",
     "start_time": "2025-01-20T13:28:02.439651Z"
    }
   },
   "outputs": [],
   "execution_count": 18
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:28:00.123514Z",
     "start_time": "2025-01-20T13:28:00.119500Z"
    }
   },
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2)) #dim=(1, 2)表示计算均值后，宽和高消除（把宽和高所有的像素加起来，再除以总数）\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ],
   "outputs": [],
   "execution_count": 17
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:27:58.370785Z",
     "start_time": "2025-01-20T13:27:58.367187Z"
    }
   },
   "source": [
    "import torch.nn as nn\n",
    "from torch.utils.data.dataloader import DataLoader    \n",
    "\n",
    "batch_size = 64\n",
    "# 从数据集到dataloader，num_workers参数不能加，否则会报错\n",
    "# https://github.com/pytorch/pytorch/issues/59438\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:28:16.208498Z",
     "start_time": "2025-01-20T13:28:15.196024Z"
    }
   },
   "source": [
    "for imgs, labels in train_loader:\n",
    "    print(imgs.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([64, 3, 128, 128])\n",
      "torch.Size([64])\n"
     ]
    }
   ],
   "execution_count": 20
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:36:50.095938Z",
     "start_time": "2025-01-20T13:36:50.042506Z"
    }
   },
   "source": [
    "\n",
    "class CNN(nn.Module):\n",
    "    def __init__(self, num_classes=10, activation=\"relu\"):\n",
    "        super(CNN, self).__init__()\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.flatten = nn.Flatten()\n",
    "        # input shape is (3, 128, 128) so the flatten output shape is 128 * 16 * 16\n",
    "        self.fc1 = nn.Linear(128 * 16 * 16, 128)\n",
    "        self.fc2 = nn.Linear(128, num_classes)\n",
    "        \n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        act = self.activation\n",
    "        x = self.pool(act(self.conv2(act(self.conv1(x)))))\n",
    "        x = self.pool(act(self.conv4(act(self.conv3(x)))))\n",
    "        x = self.pool(act(self.conv6(act(self.conv5(x)))))\n",
    "        x = self.flatten(x)\n",
    "        x = act(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return x\n",
    "    \n",
    "\n",
    "for idx, (key, value) in enumerate(CNN().named_parameters()):\n",
    "    print(f\"{key}\\tparamerters num: {np.prod(value.shape)}\")\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight\tparamerters num: 864\n",
      "conv1.bias\tparamerters num: 32\n",
      "conv2.weight\tparamerters num: 9216\n",
      "conv2.bias\tparamerters num: 32\n",
      "conv3.weight\tparamerters num: 18432\n",
      "conv3.bias\tparamerters num: 64\n",
      "conv4.weight\tparamerters num: 36864\n",
      "conv4.bias\tparamerters num: 64\n",
      "conv5.weight\tparamerters num: 73728\n",
      "conv5.bias\tparamerters num: 128\n",
      "conv6.weight\tparamerters num: 147456\n",
      "conv6.bias\tparamerters num: 128\n",
      "fc1.weight\tparamerters num: 4194304\n",
      "fc1.bias\tparamerters num: 128\n",
      "fc2.weight\tparamerters num: 1280\n",
      "fc2.bias\tparamerters num: 10\n"
     ]
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:36:52.601497Z",
     "start_time": "2025-01-20T13:36:52.555569Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def count_parameters(model): #计算模型总参数量\n",
    "    return sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "count_parameters(CNN())"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4482730"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "因为全连接层的参数量非常大，所以总体参数量多了"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:36:54.465202Z",
     "start_time": "2025-01-20T13:36:54.461084Z"
    }
   },
   "cell_type": "code",
   "source": "128*16*16*128",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4194304"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 23
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:36:57.016549Z",
     "start_time": "2025-01-20T13:36:56.424476Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ],
   "outputs": [],
   "execution_count": 24
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:37:13.514396Z",
     "start_time": "2025-01-20T13:37:01.024919Z"
    }
   },
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ],
   "outputs": [],
   "execution_count": 25
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:37:16.962093Z",
     "start_time": "2025-01-20T13:37:16.956605Z"
    }
   },
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ],
   "outputs": [],
   "execution_count": 26
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:37:19.439675Z",
     "start_time": "2025-01-20T13:37:19.435470Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 27
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:47:54.409219Z",
     "start_time": "2025-01-20T13:37:27.606563Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 20\n",
    "\n",
    "activation = \"relu\"\n",
    "model = CNN(num_classes=10, activation=activation)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001, eps=1e-7)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/monkeys-cnn-{activation}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, img_h, img_w])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/monkeys-cnn-{activation}\", save_step=len(train_loader), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/360 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "91b057ed53cf4787990446dcf5ddecf1"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 13 / global_step 234\n"
     ]
    }
   ],
   "execution_count": 28
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:52:42.264741Z",
     "start_time": "2025-01-20T13:52:42.077684Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 29
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T13:53:08.038481Z",
     "start_time": "2025-01-20T13:53:01.402173Z"
    }
   },
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/monkeys-cnn-{activation}/best.ckpt\",weights_only=True, map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     1.8048\n",
      "accuracy: 0.6581\n"
     ]
    }
   ],
   "execution_count": 31
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
